iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
0
Modern Web

從巨人的 Tip 看 Angular系列 第 10

[Day 10] 深度看一下 Angular 建立 multi provider 的機制

  • 分享至 

  • xImage
  •  

前幾天的文章中介紹了 multi provider 的效果。今天要來認真的看一下 Angular 是怎麼處理 multi provider 的。

在 @Compontnet 裝飾詞上加上 providers 陣列,經過 compiler 編譯之後,會由一個名字是 ɵɵProvidersFeature 的函式來處理傳入的 provider 物件,以下是 Day 8 文章內的 AppComponent 經過編譯後產生的 JavaScript 程式碼:

AppComponent.ɵcmp = _angular_core__WEBPACK_IMPORTED_MODULE_0__[
  "ɵɵdefineComponent"
]({
  type: AppComponent,
  selectors: [["app-root"]],
  features: [
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵProvidersFeature"]([
      {
        provide: MULTI_TOKEN,
        useValue: "OK",
        multi: true,
      },
      {
        provide: MULTI_TOKEN,
        useValue: "OOOOOK",
        multi: true,
      },
    ]),
  ],
  decls: 1,
  vars: 0,
  template: function AppComponent_Template(rf, ctx) {
    if (rf & 1) {
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelement"](0, "app-new");
    }
  },
  styles: [
    "\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuc2NzcyJ9 */",
  ],
});

↑ Block 1

在 Block 1 中可以清楚的看到 compile 後的程式碼寫著 ɵɵProvidersFeature

再來就可以開始爬了!

先看一下 ɵɵProvidersFeature 函式的實作

export function ɵɵProvidersFeature<T>(providers: Provider[], viewProviders: Provider[] = []) {
  return (definition: DirectiveDef<T>) => {
    definition.providersResolver =
        (def: DirectiveDef<T>, processProvidersFn?: ProcessProvidersFunction) => {
          return providersResolver(
              def,                                                             //
              processProvidersFn ? processProvidersFn(providers) : providers,  //
              viewProviders);
        };
  };
}

↑ Block 2

這個函式基本上就是會回傳另一個 function,並運用到了 JavaScript Closure 的概念(對 Closure 沒概念的話可以看一下這篇),把從 @Component 取得 providers 與 viewProviders 包在回傳的 function 內。

當 Angular 透過 ɵɵdefineComponent 函式來建立 component 的 definition object 時,這個 ɵɵProvidersFeature 回傳的函式就會被呼叫,同時會將 providersResolver 函式 assign 給這個 definition object 的 providersResolver 屬性。

providersResolver 函式的實作內容

export function providersResolver<T>(
    def: DirectiveDef<T>, providers: Provider[], viewProviders: Provider[]): void {
  const tView = getTView();
  if (tView.firstCreatePass) {
    const isComponent = isComponentDef(def);

    // The list of view providers is processed first, and the flags are updated
    resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true);

    // Then, the list of providers is processed, and the flags are updated
    resolveProvider(providers, tView.data, tView.blueprint, isComponent, false);
  }
}

↑ Block 3

分開就 viewProviders 與 providers 使用 resolveProvider 函式做處理,來看一下 resolveProvider實作內容

function resolveProvider(
    provider: Provider, tInjectables: TData, lInjectablesBlueprint: NodeInjectorFactory[],
    isComponent: boolean, isViewProvider: boolean): void {
  provider = resolveForwardRef(provider);
  if (Array.isArray(provider)) {
    // Recursively call `resolveProvider`
    // Recursion is OK in this case because this code will not be in hot-path once we implement
    // cloning of the initial state.
    for (let i = 0; i < provider.length; i++) {
      resolveProvider(
          provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider);
    }
  } else {
    const tView = getTView();
    const lView = getLView();
    let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
    let providerFactory: () => any = providerToFactory(provider);

    const tNode = getPreviousOrParentTNode();
    const beginIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
    const endIndex = tNode.directiveStart;
    const cptViewProvidersCount =
        tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;

    if (isTypeProvider(provider) || !provider.multi) {
      // ... 略
    } else {
      // ... 略

      const existingProvidersFactoryIndex =
          indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex);
      const existingViewProvidersFactoryIndex =
          indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount);
      const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 &&
          lInjectablesBlueprint[existingProvidersFactoryIndex];
      const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 &&
          lInjectablesBlueprint[existingViewProvidersFactoryIndex];

      if (isViewProvider && !doesViewProvidersFactoryExist ||
          !isViewProvider && !doesProvidersFactoryExist) {
        // 下接 Block 5
      } else {
        // 下接 Block 8
      }
      if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
        lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders!++;
      }
    }
  }
}

↑ Block 4

當 Angular 發現現在在解析的 provider 沒有出現在 lInjectables、也沒有對應的 factory 就會進到 Block 5:

diPublicInInjector(
    getOrCreateNodeInjectorForNode(
        tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
    tView, token);
const factory = multiFactory(
    isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
    lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory);
if (!isViewProvider && doesViewProvidersFactoryExist) {
  lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
}
registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0);
tInjectables.push(token);
tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) {
  tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
}
lInjectablesBlueprint.push(factory);
lView.push(factory);

↑ Block 5

Block 5 主要會先透過 diPublicInInjector 將 token 放入 bloom filter 內,並透過 multiFactory 函式產生一個 NodeInjectoryFactory 物件。

multiFactory 的第一個傳入參數就是日後 Angular 實際用來產生實體的工廠方法。

以這邊的例子來看,會使用 multiProvidersFactoryResolver 函式:

function multiProvidersFactoryResolver(
    this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView,
    tNode: TDirectiveHostNode): any[] {
  return multiResolve(this.multi!, []);
}

↑ Block 6

function multiResolve(factories: Array<() => any>, result: any[]): any[] {
  for (let i = 0; i < factories.length; i++) {
    const factory = factories[i]! as () => null;
    result.push(factory());
  }
  return result;
}

↑ Block 7:真正用來回傳實體的函式 multiResolve

如果 Angular 發現要解析的 provider 已經存在於 lInjectables 的話,就會直接使用 multiFactoryAdd 這個函式將傳入的 provider 放入已存在的 NodeInjectFactory 內,如 Block 8:

const indexInFactory = multiFactoryAdd(
    lInjectablesBlueprint!
        [isViewProvider ? existingViewProvidersFactoryIndex :
                          existingProvidersFactoryIndex],
    providerFactory, !isViewProvider && isComponent);
registerDestroyHooksIfSupported(
    tView, provider,
    existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex :
                                         existingViewProvidersFactoryIndex,
    indexInFactory);

↑ Block 8


以上就是從宣告 multi provider → 放入 injector 一路到解析出實體的相關程式碼!

進展到雙位數啦 ?

以下按照入團順序列出我們團隊夥伴的系列文章!


上一篇
[Day 9] 事件繫結好方便,那你知道 EVENT_MANAGER_PLUGINS 嗎?
下一篇
[Day 11] 關於 @Self、@Optional、@SkipSelf 的二三事與 @Host 的陷阱
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言